#include "RenderMethodDirectDraw.h"

#undef WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>

#include "SpriteDirectDraw.h"
#include "BackgroundDirectDraw.h"
#include "SurfaceDirectDraw.h"
#include "ViewportDirectDraw.h"

#include "TinyImageLoader.h"

namespace YoghurtGum
{

	RenderMethodDirectDraw* RenderMethodDirectDraw::s_Instance = NULL;

	unsigned long RenderMethodDirectDraw::GetFillColor(Color a_Color)
	{
		unsigned long R = 
			(a_Color.r > 1.f) ? m_DDPixelFormat.dwRBitMask : 
			(a_Color.r < 0.f) ? 0x0000000 : 
			(unsigned long)(a_Color.r * (float)m_DDPixelFormat.dwRBitMask);

		unsigned long G = 
			(a_Color.g > 1.f) ? m_DDPixelFormat.dwGBitMask : 
			(a_Color.g < 0.f) ? 0x0000000 : 
			(unsigned long)(a_Color.g * (float)m_DDPixelFormat.dwGBitMask);

		unsigned long B = 
			(a_Color.b > 1.f) ? m_DDPixelFormat.dwBBitMask : 
			(a_Color.b < 0.f) ? 0x0000000 : 
			(unsigned long)(a_Color.b * (float)m_DDPixelFormat.dwBBitMask);

		unsigned long final = m_DDPixelFormat.dwRGBAlphaBitMask | R | G | B;

		return (final);
	}

	Surface* RenderMethodDirectDraw::GetRenderTarget()
	{
		//return m_DDBackBuffer;
		return m_DDFrontBuffer;
	}

	RenderMethodDirectDraw::RenderMethodDirectDraw() : RenderMethod()
	{
		strcpy(m_PluginName, "DirectDraw");
	}

	bool RenderMethodDirectDraw::Init()
	{
		til::TinyImageLoader::Init();

		if (ErrorDirectDraw::Failed(DirectDrawCreate(
					NULL, 
					&m_DDObject, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to initialize DirectDraw.");
			return false;
		}

		DDDEVICEIDENTIFIER driver;
		ZeroMemory(&driver, sizeof(driver));

		if (ErrorDirectDraw::Failed(m_DDObject->GetDeviceIdentifier(&driver, NULL)))
		{
			ERROR_EXPLAIN("Failed to get driver information.");
		}
		else
		{
			Log::GetSingleton() << "Driver: " << driver.szDriver << "" << ENDL;
		}

		ZeroMemory(&m_DDSurfaceDesc, sizeof(m_DDSurfaceDesc));
		m_DDSurfaceDesc.dwSize = sizeof(m_DDSurfaceDesc);

		if (ErrorDirectDraw::Failed(m_DDObject->GetDisplayMode(
					&m_DDSurfaceDesc
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to get display mode.");
			return false;
		}

		DDCAPS emulation_caps;
		if (ErrorDirectDraw::Failed(m_DDObject->GetCaps(
					&m_DDCaps, 
					&emulation_caps
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to get DirectDraw caps.");
			return false;
		}

		Log::GetSingleton().Print("Caps: 0x%x", m_DDCaps.ddsCaps.dwCaps);

		if ((~m_DDCaps.ddsCaps.dwCaps | DDSCAPS_VALID) != 0xFFFFFFFF)
		{
			ERROR_EXPLAIN("Your drivers are using unsupported flags. (dwCaps: 0x%x)", m_DDCaps.ddsCaps.dwCaps);
			return false;
		}


		unsigned int memory;
		if (m_DDCaps.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY)
		{		 
			Log::GetSingleton() << "Using video memory." << ENDL; 
			memory = DDSCAPS_VIDEOMEMORY;
		}
		else
		{ 
			Log::GetSingleton() << "Using system memory." << ENDL; 
			memory = DDSCAPS_SYSTEMMEMORY;
		}
	
		Log::GetSingleton() << "Available video memory: " << YG_BYTES_TO_MEGABYTES(m_DDCaps.dwVidMemFree) << " / " << YG_BYTES_TO_MEGABYTES(m_DDCaps.dwVidMemTotal) << " MB." << ENDL;
			
		if (m_DDCaps.ddsCaps.dwCaps & DDSCAPS_BACKBUFFER || m_DDCaps.ddsCaps.dwCaps & DDSCAPS_FLIP) 
		{
			Log::GetSingleton() << "Using double buffering." << ENDL;
			m_BufferMode = BUFFER_DOUBLE;
			m_DDSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
			m_DDSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | memory;
			m_DDSurfaceDesc.dwBackBufferCount = 1;
		}
		else
		{
			Log::GetSingleton() << "Using single buffer." << ENDL;
			m_BufferMode = BUFFER_SINGLE;
			m_DDSurfaceDesc.dwFlags = DDSD_CAPS;
			m_DDSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | memory;
		}

		// go to full screen
		SetFullScreen();
		
		HRESULT surf_result;

		surf_result = 
			m_DDObject->CreateSurface(
					&m_DDSurfaceDesc, 
					&m_DDFront, 
					NULL
			);

		/*sprintf(buffer, "dwCaps: 0x%x", m_DDSurfaceDesc.ddsCaps.dwCaps);
		Log::GetSingleton() << buffer << "" << ENDL;*/

		if (ErrorDirectDraw::Failed(surf_result)) 
		{ 
			bool failed = true;

			if (surf_result == E_OUTOFMEMORY)
			{
				m_DDSurfaceDesc.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY;
				m_DDSurfaceDesc.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

				char buffer[256];
				sprintf(buffer, "dwCaps: 0x%x", m_DDSurfaceDesc.ddsCaps.dwCaps);
				Log::GetSingleton() << buffer << "" << ENDL;

				Log::GetSingleton() << "Not enough video memory, retrying using system memory." << ENDL;

				if (ErrorDirectDraw::Failed(m_DDObject->CreateSurface(
							&m_DDSurfaceDesc, 
							&m_DDFront, 
							NULL
						)
					)
				)
				{
					failed = true;
				}
			}
			
			if (failed)
			{
				ERROR_EXPLAIN("Failed to create frontbuffer surface.");
				return false;
			}
		}

		Log::GetSingleton() << "Screen dimensions (" << GetSystemMetrics(SM_CXSCREEN) << ", " << GetSystemMetrics(SM_CYSCREEN) << ")" << ENDL;

		// initial viewport
		SetViewport(CreateViewport(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)));

		m_DDBack = ((ViewportDirectDraw*)m_ViewportCurrent)->GetBuffer();

		LPDIRECTDRAWCLIPPER clipper;
		m_DDBack->GetClipper(&clipper);
		Log::GetSingleton().Print("Clipper: 0x%x", clipper);

		GetBackBuffer()->GetClipper(&clipper);
		Log::GetSingleton().Print("Clipper: 0x%x", clipper);

		ZeroMemory(&m_DDPixelFormat, sizeof(m_DDPixelFormat));
		m_DDPixelFormat.dwSize = sizeof(m_DDPixelFormat);
		m_DDPixelFormat.dwRGBBitCount = 32;

		// get pixel format
		m_DDFront->GetPixelFormat(&m_DDPixelFormat);
		Log::GetSingleton() << "Pixel format: " << m_DDPixelFormat.dwRGBBitCount << "-bits." << ENDL;

		Log::GetSingleton() << "Maximum allowed number of overlays: " << m_DDCaps.dwMaxVisibleOverlays << "" << ENDL;

		// create the m_clipper
		if (ErrorDirectDraw::Failed(m_DDObject->CreateClipper(
					0,
					&m_DDClipper, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to create clipping device.");
			return false;
		}

		// attach it to the window
		if (ErrorDirectDraw::Failed(m_DDClipper->SetHWnd(
					0, 
					m_WindowHandle
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to attach clipping device to window.");
			return false;
		}

		// attach it to the backbuffer
		if (ErrorDirectDraw::Failed(m_DDFront->SetClipper(
					m_DDClipper
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to attach clipping device to backbuffer.");
			return false;
		}

		if (ErrorDirectDraw::Failed(m_DDFront->Restore()))
		{
			ERROR_EXPLAIN("Failed to restore frontbuffer.");
			return false;
		}
		if (ErrorDirectDraw::Failed(m_DDBack->Restore()))
		{
			ERROR_EXPLAIN("Failed to restore backbuffer.");
			return false;
		}

		m_ImageDataFilled = 0;

		// set the device's size rectangle
		SetRect(
			&m_Dimensions, 
			0, 0, 
			GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)
		);

		return true;
	}

	void RenderMethodDirectDraw::CleanUp()
	{

	}

	void RenderMethodDirectDraw::SetClearColor(Color a_Color)
	{
		ZeroMemory(&m_DDClearSettings, sizeof(m_DDClearSettings));
		m_DDClearSettings.dwSize = sizeof(m_DDClearSettings);
		m_DDClearSettings.dwFillColor = GetFillColor(a_Color);
	}

	void RenderMethodDirectDraw::SetFullScreen()
	{
		if (ErrorDirectDraw::Failed(m_DDObject->SetCooperativeLevel(
					m_WindowHandle, 
					DDSCL_FULLSCREEN
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to set fullscreen mode.");
		}
	}

	bool RenderMethodDirectDraw::Clear()
	{
		if  (ErrorDirectDraw::Failed(m_DDBack->Blt (
					NULL, 
					NULL,
					NULL, 
					DDBLT_COLORFILL, 
					&m_DDClearSettings
				)
			)
		)
		{
			ERROR_EXPLAIN("Couldn't clear the screen.");
			return false;
		}

		return true;
	}

	char* RenderMethodDirectDraw::GetName()
	{
		return m_PluginName;
	}

	bool RenderMethodDirectDraw::PreRender()
	{
		return m_ViewportCurrent->Enable();
	}

	bool RenderMethodDirectDraw::PostRender()
	{
		return m_ViewportCurrent->Disable();
	}

	RenderMethodDirectDraw* RenderMethodDirectDraw::GetSingleton()
	{
		if (!s_Instance) { s_Instance = new RenderMethodDirectDraw(); }
		return s_Instance;
	}

	bool RenderMethodDirectDraw::LoadImage(const char* a_File, const char* a_Name, Color a_AlphaKey)
	{
		Log::GetSingleton().Print("Loading image '%s'.", a_File);

		til::Image* image = til::TinyImageLoader::Load(a_File, TIL_FILE_ADDWORKDINGDIR | TIL_DEPTH_R5G6B5);
		if (image == NULL)
		{
			ERROR_EXPLAIN("Failed to load image.");
			return false;
		}

		ImageMap* data = &m_ImageData[m_ImageDataFilled];
		strcpy(data->file, a_File);
		strcpy(data->name, a_Name);
		data->data = new ImageData;
		data->frames = 1;

		ImageData* current = data->data;

		current->props = YG_SPRITE_QUALITY_MEDIUM;

		if (a_AlphaKey != Color::CNONE)
		{
			current->props |= YG_SPRITE_TRANSPARANCY_KEYING;
			current->key = a_AlphaKey;
		}

		unsigned long width  = image->GetWidth();
		unsigned long height = image->GetHeight();

		ZeroMemory(&current->desc, sizeof(current->desc));
		current->desc.dwSize    = sizeof(current->desc);
		current->desc.dwFlags   = DDSD_WIDTH | DDSD_HEIGHT;
		current->desc.dwWidth   = width;
		current->desc.dwHeight  = height;

		SetRect(
			&current->dim, 
			0, 0, 
			width, height
		);

		if (ErrorDirectDraw::Failed(m_DDObject->CreateSurface (
					&current->desc,
					&current->surf,
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to create image surface.");
			return false;
		}

		// copying tinyimageloader data to a surface

		DDSURFACEDESC desc;
		ZeroMemory(&desc, sizeof(desc));
		desc.dwSize = sizeof(desc);

		if (ErrorDirectDraw::Failed(current->surf->Lock(
					NULL, 
					&desc, 
					0, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to lock image surface.");
			return false;
		}

		memcpy (
			desc.lpSurface, 
			image->GetPixels(), 
			width * height * 2
		);

		if (ErrorDirectDraw::Failed(current->surf->Unlock(
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to unlock surface.");
			return false;
		}

		m_ImageDataFilled++;

		return true;
	}

	Sprite* RenderMethodDirectDraw::CreateSprite(const char* a_Name)
	{
		bool found = false;
		uint32 i;
		for (i = 0; i < m_ImageDataFilled; i++)
		{
			if (!strcmp(m_ImageData[i].name, a_Name))
			{
				found = true;
				break;
			}
		}

		if (!found) 
		{
			ERROR_EXPLAIN("Could not find sprite named '%s'", a_Name);
			return NULL; 
		}
		
		if (m_SpriteList[m_SpriteTotal]) { delete m_SpriteList[m_SpriteTotal]; }

		SpriteDirectDraw* current = new SpriteDirectDraw();
		current->SetData(m_ImageData[i].data);
		m_SpriteList[m_SpriteTotal++] = current;

		return (current);
	}

	SpriteEx* RenderMethodDirectDraw::CreateSpriteEx(const char* a_Name, uint32 a_Flags /*= YG_SPRITEEX_DEFAULT*/)
	{
		YG_STUB;
		return NULL;
	}

	Background* RenderMethodDirectDraw::CreateBackground(const char* a_Name, uint32 a_Flags /* = YG_BACKGROUND_DEFAULT */)
	{
		//return NULL;

		bool found = false;
		uint32 i;
		for (i = 0; i < m_ImageDataFilled; i++)
		{
			if (!strcmp(m_ImageData[i].name, a_Name))
			{
				found = true;
				break;
			}
		}

		if (!found) 
		{
			ERROR_EXPLAIN("Could not find image named '%s'", a_Name);
			return NULL; 
		}

		BackgroundDirectDraw* current = new BackgroundDirectDraw();
		current->SetData(m_ImageData[i].data);
		m_Backgrounds[m_BackgroundsFilled++] = current;

		return (current);
	}

	Surface* RenderMethodDirectDraw::CreateSurface(int a_Width, int a_Height, uint32 a_Flags)
	{
		return (new SurfaceDirectDraw(a_Width, a_Height, a_Flags));
	}

	uint32 RenderMethodDirectDraw::CreateViewport(uint32 a_Width, uint32 a_Height, uint32 a_Type /*= YG_VIEWPORT_TYPE_NOTHING*/)
	{
		m_ViewportList[m_ViewportTotal] = new ViewportDirectDraw(a_Width, a_Height, a_Type);
		return (m_ViewportTotal++);
	}

	bool RenderMethodDirectDraw::SetViewport(uint32 a_ID)
	{
		if (m_ViewportList[a_ID])
		{
			m_ViewportCurrent = m_ViewportList[a_ID];
			ViewportDirectDraw* view = (ViewportDirectDraw*)m_ViewportCurrent;
			m_DDBack = view->GetBuffer();
			view->SetTarget(m_DDFront);
			return true;
		}
	
		ERROR_EXPLAIN("There is no viewport assigned to id %i.", a_ID);
		return false;
	}

	Viewport* RenderMethodDirectDraw::GetViewport(uint32 a_ID)
	{
		if (m_ViewportList[a_ID]) { return m_ViewportList[a_ID]; }
		return NULL;
	}

	LPDIRECTDRAWSURFACE RenderMethodDirectDraw::GetBackBuffer()
	{
		ViewportDirectDraw* view = (ViewportDirectDraw*)m_ViewportCurrent;
		return view->GetBuffer();
	}

	uint32 RenderMethodDirectDraw::GetBackbufferWidth()
	{
		return m_ViewportCurrent->GetWidth();
	}

	uint32 RenderMethodDirectDraw::GetBackbufferHeight()
	{
		return m_ViewportCurrent->GetHeight();
	}

	void RenderMethodDirectDraw::Resize(int a_Width, int a_Height)
	{
		YG_STUB;
	}

	extern "C" YG_DLL_EXPORT RenderMethod* Install(Device* a_Device)
	{
		Device::SetInstance(a_Device);

		return ((RenderMethod*)RenderMethodDirectDraw::GetSingleton());
	}

}; // namespace YoghurtGum